﻿using System;
using System.Collections.Generic;
using System.Text;

namespace SsLib
{
    public abstract class Simulation : SimSet
    {
        private Head SQS;

        internal class EVENT_NOTICE : Link
        {
            private Simulation _outer;
            internal double EVTIME;
            internal Process PROC;

            public EVENT_NOTICE(Simulation _outer, double evtime, Process proc)
            {
                this._outer = _outer;
                this.EVTIME = evtime;
                this.PROC = proc;
            }

            public EVENT_NOTICE Suc()
            {
                return this.SUC as EVENT_NOTICE;
            }

            public EVENT_NOTICE Pred()
            {
                return (EVENT_NOTICE) this.PRED;
            }

            internal void RANK_IN_SQS(bool afore)
            {
                EVENT_NOTICE evt;
                evt = (EVENT_NOTICE) this._outer.SQS.Last();

                while (evt.EVTIME > this.EVTIME) evt = evt.Pred();

                if (afore)
                    while (evt.EVTIME == this.EVTIME) evt = evt.Pred();

                this.Follow(evt);
            }
        }

        public MAIN_PROGRAM Main;


        //must be hidden sine it should be inaccessible from usercode
        internal EVENT_NOTICE FIRSTEV()
        {
            return (EVENT_NOTICE) this.SQS.First();
        }

        public Process Current()
        {
            return this.FIRSTEV().PROC;
        }

        public double Time()
        {
            return this.FIRSTEV().EVTIME;
        }

        public class Process : Link
        {
            private Simulation _outer;

            internal EVENT_NOTICE EVENT;
            internal bool TERMINATED;

            public bool Idle()
            {
                return (this.EVENT == null);
            }

            public bool Terminated()
            {
                return this.TERMINATED;
            }

            public double EvTime()
            {
                if (Idle()) Environment.Error("EvTime: No Evtime for idle process.");

                return this.EVENT.EVTIME;
            }

            public Process NextEv()
            {
                if (Idle() || EVENT.Suc() == null)
                    return null;
                else
                    return EVENT.Suc().PROC;
            }

            public Process(Simulation _outer)
            {
                this._outer = _outer;
            }

            public override void Prolog()
            {
                base.Prolog();
                this.Detach();
            }

            public override void Epilog()
            {
                this.TERMINATED = true;
                this._outer.Passivate();

                Environment.Error("Process: Terminated process.");

                base.Epilog();
            }
        }

        private void ACTIVAT(bool REAC, Process X, Text CODE, double T, Process Y, bool PRIO)
        {
            //Value transfer of parameter CODE
            Text dummy = BasicIO._outer.Blanks(CODE.Length());
            dummy.SetValue(CODE);
            CODE = dummy;

            //inspect X
            if (X != null && !X.TERMINATED)
            {
                Process z;
                EVENT_NOTICE EV = null;

                if (REAC) EV = X.EVENT;
                else if (X.EVENT != null) goto exit;

                z = this.Current();

                switch (CODE.ToString())
                {
                    case "direct":
                        X.EVENT = new EVENT_NOTICE(this, Time(), X);
                        X.EVENT.Precede(FIRSTEV());
                        break;
                    case "delay":
                        T = T + Time();
                        goto case "at";
                    case "at":
                        if (T < Time()) T = Time();
                        if (T == Time() && PRIO) goto case "direct";
                        X.EVENT = new EVENT_NOTICE(this, T, X);
                        X.EVENT.RANK_IN_SQS(PRIO);
                        break;
                    default:
                        if (Y == null || Y.EVENT == null)
                        {
                            X.EVENT = null;
                        }
                        else
                        {
                            if (X == Y) goto exit;

                            // reactivate X before/after X.
                            X.EVENT = new EVENT_NOTICE(this, Y.EVENT.EVTIME, X);

                            if (CODE.ToString().Equals("before"))
                            {
                                X.EVENT.Precede(Y.EVENT);
                            }
                            else
                            {
                                X.EVENT.Follow(Y.EVENT);
                            }
                        }
                        break;
                }

                if (EV != null)
                {
                    EV.Out();
                    if (SQS.Empty()) Environment.Error("Activate: faen.");
                }
                if (z != Current()) BasicIO._outer.Resume(Current());
            }

        exit: dummy = null;  //we must use a dummy statement for label to work
        }

        public void Hold(double T)
        {
            //inspect FIRSTEV
            EVENT_NOTICE fev = FIRSTEV();
            if (fev != null)
            {
                if (T > 0) fev.EVTIME = fev.EVTIME + T;
                if (fev.Suc() != null && fev.Suc().EVTIME <= fev.EVTIME)
                {
                    fev.Out();
                    fev.RANK_IN_SQS(false);
                    BasicIO._outer.Resume(Current());
                }
            }
        }

        public void Passivate()
        {
            //inspect Current
            Process prc = Current();
            if (prc != null)
            {
                prc.EVENT.Out();
                prc.EVENT = null;
            }
            if (SQS.Empty())
                Environment.Error("Passivate: faen");
            else
                BasicIO._outer.Resume(Current());
        }

        public void Wait(Head S)
        {
            Current().Into(S);
            Passivate();
        }

        public void Cancel(Process X)
        {
            if (X == Current()) Passivate();

            //inspect X
            if (X != null)
            {
                if (X.EVENT != null)
                {
                    X.EVENT.Out();
                    X.EVENT = null;
                }
            }
        }

        //must be hidden sine it should be inaccessible from usercode (like BasicIO)
        public class MAIN_PROGRAM : Process
        {
            public MAIN_PROGRAM(Simulation _outer)
                : base(_outer)
            {
            }

            public override void Prolog()
            {
                base.Prolog();

                while (true) Detach();
            }
        }


        //Name parameters to Accum, shit, first 3 of them
        //TODO: Guess we need to implement real name parameters then,
        //      and quit relying on ByRef.
        //however, all are assigned to, so i believe ByRef parameters will do fine.
        // phhuuuhhh =)
        public void Accum(ref double a, ref double b, ref double c, double d)
        {
            a = a + c * (Time() - b);
            b = Time();
            c = c + d;
        }

        public Simulation() : base()
        {
            this.SQS = new Head();
            this.Main = new MAIN_PROGRAM(this);
            this.Main.EVENT = new EVENT_NOTICE(this, 0, this.Main);
            this.Main.EVENT.Into(this.SQS);
        }
    }
}
